#include "modelvalidator.h"

#include "../documents/data/graphicdata.h"
#include "../modelvalidator/ivalidatorrule.h"

#include "documentmanager.h"

#include <iconmanager.h>
#include <icontainerdocument.h>
#include <idocument.h>
#include <mimetype.h>

#include <QtGui/QAction>
#include <QtGui/QStandardItemModel>

using namespace core;
using namespace swp;

typedef QSharedPointer<IDocument> Document;

ModelValidator* modelValidator = 0;

ModelValidator* ModelValidator::getInstance()
{
    if (modelValidator == 0)
    {
        modelValidator = new ModelValidator();
    }

    return modelValidator;
}

void ModelValidator::destroy()
{
    delete modelValidator;
    modelValidator = 0;
}

void ModelValidator::addRule(IValidatorRule* rule, const MimeType& forType)
{
    if (!m_rules.contains(forType.name()))
    {
        QList<IValidatorRule* > list;
        list << rule;
        m_rules[forType.name()] = list;
    }
    else
    {
        m_rules[forType.name()].append(rule);
    }
}

QStandardItemModel* ModelValidator::report() const
{
    return m_report;
}

ModelValidator::ModelValidator() : QObject(),
    m_rules(),
    m_report(0),
    m_selectActions(),
    m_validatedProject(0)
{
    m_report = new QStandardItemModel(this);

    IconManager* im = IconManager::getInstance();
    im->registerStyleIcon("SwpValidatorPassedIcon",
        ":/swp/icons/validator_passed_icon.png");
    im->registerStyleIcon("SwpValidatorErrorIcon",
        ":/swp/icons/validator_error_icon.png");
}

ModelValidator::~ModelValidator()
{
    foreach (const QList<IValidatorRule* >& rules, m_rules.values())
    {
        foreach (IValidatorRule* rule, rules)
        {
            delete rule;
        }
    }
}

QSharedPointer<IDocument> ModelValidator::getDocument(QAction* action) const
{
    QSharedPointer<IDocument> ret;

    const QString& data = action->data().toString();

    if (!data.isEmpty())
    {
        const QStringList splitted =
            data.split("|", QString::SkipEmptyParts);

        const QList<QSharedPointer<IDocument> > docs =
            m_validatedProject->documents(splitted.first());

        const int index = splitted.last().toInt();

        if (index >= 0 && index < docs.size())
        {
            ret = docs.at(index);
        }
    }

    return ret;
}

// public slots
void ModelValidator::validate(
    const QSharedPointer<IContainerDocument>& project)
{
    if (!project.isNull())
    {
        emit validatingStarted();

        m_validatedProject = project;

        m_report->clear();

        foreach (QAction* action, m_selectActions)
        {
            delete action;
        }

        m_selectActions.clear();
        m_parentActionMapping.clear();

        QAction* selectProjectAction = new QAction("", this);
        m_selectActions.append(selectProjectAction);

        connect(selectProjectAction, SIGNAL(triggered()), this,
            SLOT(selectProject()));

        QStandardItem* projectItem = new QStandardItem(project->name());
        projectItem->setEditable(false);
        projectItem->setData(
            QVariant::fromValue(reinterpret_cast<void* >(selectProjectAction)),
            Qt::UserRole);

        QStandardItem* projectItemErrors = new QStandardItem(tr("Errors"));
        projectItemErrors->setEditable(false);

        bool projectPassed = true;

        if (m_rules.contains(project->mimeType().name()))
        {
            const QList<IValidatorRule* > rules = m_rules[
                project->mimeType().name()];

            foreach (IValidatorRule* rule, rules)
            {
                if (rule->isProjectWide())
                {
                    QAction* action = 0;
                    QString text;
                    const bool passed = rule->validate(project, action, text);

                    if (!passed)
                    {
                        projectPassed = false;

                        QStandardItem* error = new QStandardItem(text);
                        error->setEditable(false);
                        if (action != 0)
                        {
                            m_parentActionMapping[action] =
                                selectProjectAction;
                            error->setData(
                                QVariant::fromValue(
                                    reinterpret_cast<void* >(action)),
                                Qt::UserRole);
                        }
                        else
                        {
                            action = selectProjectAction;
                        }

                        error->setData(
                            QVariant::fromValue(
                                reinterpret_cast<void* >(action)),
                            Qt::UserRole);

                        projectItemErrors->appendRow(error);
                    }
                }
            }
        }

        project->setValid(projectPassed);

        if (projectItemErrors->rowCount()> 0)
        {
            projectItem->appendRow(projectItemErrors);
        }

        const QStringList categories = project->categories();

        QList<Document> openedToMemory;

        foreach (const QString& category, categories)
        {
            const QList<QSharedPointer<IDocument> > documents =
                project->documents(category);

            int documentIndex = 0;
            foreach (const QSharedPointer<IDocument>& document, documents)
            {
                if (document->isFuture())
                {
                    DocumentManager::getInstance()->openDocumentToMemory(
                        document);

                    openedToMemory.append(document);
                }

                const QString documentType = document->mimeType().name();

                QAction* selectDocumentAction = new QAction("", this);
                selectDocumentAction->setData(
                    QVariant(QString("%1|%2").arg(category).arg(
                        documentIndex)));

                m_selectActions.append(selectDocumentAction);

                connect(selectDocumentAction, SIGNAL(triggered()), this,
                    SLOT(selectDocument()));

                QStandardItem* documentItem = new QStandardItem(
                    document->name());
                documentItem->setEditable(false);
                documentItem->setData(
                    QVariant::fromValue(reinterpret_cast<void* >(
                        selectDocumentAction)),
                    Qt::UserRole);

                bool allPassed = true;

                if (m_rules.contains(documentType))
                {
                    const QList<IValidatorRule* > rules =
                        m_rules[documentType];

                    foreach (IValidatorRule* rule, rules)
                    {
                        QAction* action = 0;
                        QString text;
                        const bool passed = rule->validate(
                            document, action, text);

                        if (!passed)
                        {
                            allPassed = false;
                            projectPassed = false;

                            QStandardItem* error = new QStandardItem(text);
                            error->setEditable(false);
                            if (action != 0)
                            {
                                m_parentActionMapping[action] =
                                    selectDocumentAction;
                            }
                            else {
                                action = new QAction("", this);
                                action->setData(QVariant(QString("%1|%2").arg(
                                    category).arg(documentIndex)));
                                m_selectActions.append(action);
                                m_parentActionMapping[action] =
                                    selectDocumentAction;

                                connect(action, SIGNAL(triggered()),
                                        this, SLOT(deselectItems()));
                            }

                            error->setData(
                                QVariant::fromValue(
                                    reinterpret_cast<void* >(action)),
                                Qt::UserRole);

                            documentItem->appendRow(error);
                        }
                    }

                    ++documentIndex;
                }

                IconManager* im = IconManager::getInstance();
                if (im->hasIcon("SwpValidatorPassedIcon") &&
                    im->hasIcon("SwpValidatorErrorIcon"))
                {
                    documentItem->setIcon(allPassed ?
                        im->icon("SwpValidatorPassedIcon") :
                        im->icon("SwpValidatorErrorIcon"));
                }

                document->setValid(allPassed);

                projectItem->appendRow(documentItem);
            }
        }

        foreach (const Document& document, openedToMemory)
        {
            DocumentManager::getInstance()->closeDocument(document);
        }

        IconManager* im = IconManager::getInstance();
        if (im->hasIcon("SwpValidatorPassedIcon") &&
            im->hasIcon("SwpValidatorErrorIcon"))
        {
            projectItem->setIcon(projectPassed ?
                im->icon("SwpValidatorPassedIcon") :
                im->icon("SwpValidatorErrorIcon"));
        }

        m_report->appendRow(projectItem);

        emit validatingFinished();
    }
}

void ModelValidator::handleReportItemActivation(const QModelIndex& index)
{
    QStandardItem* item = m_report->itemFromIndex(index);

    if (item != 0)
    {
        const QVariant data = item->data(Qt::UserRole);
        QAction* action = reinterpret_cast<QAction* >(data.value<void* >());

        if (action != 0)
        {
            action->trigger();
        }
    }
}

void ModelValidator::actionAboutToTrigger(QAction* action)
{
    if (m_parentActionMapping.contains(action))
    {
        m_parentActionMapping[action]->trigger();
    }
}

void ModelValidator::selectDocument()
{
    Q_ASSERT(!m_validatedProject.isNull());
    // can't trigger action that shoulnd't exist

    QAction* action = qobject_cast<QAction* >(sender());

    if (action != 0)
    {
        emit highlightDocumentRequest(getDocument(action));
    }
}

void ModelValidator::selectProject()
{
    Q_ASSERT(!m_validatedProject.isNull());
    // can't trigger action that shoulnd't exist

    emit selectProjectRequest(m_validatedProject);
}

void ModelValidator::deselectItems()
{
    Q_ASSERT(!m_validatedProject.isNull());
    // can't trigger action that shoulnd't exist

    QAction* action = qobject_cast<QAction* >(sender());

    if (action != 0)
    {
        IDocumentData* data = getDocument(action)->data();

        if (data->type() == IDocumentData::GraphicModel)
        {
            GraphicData* _data = dynamic_cast<GraphicData* >(data);

            Q_ASSERT(data != 0); // all GraphicModel should inherit GraphicData

            _data->clearSelection();
        }
    }
}
